﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Legend.Threading;
using FakeItEasy; 
using Legend.Collections;
using System.Threading;

namespace Legend.Core.Tests.Threading.ThreadingExtensionsTests
{
    [TestFixture]
    public class ISynchronizationManager_AcquireReadLock
    {
        ISynchronizationManager manager;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            manager = A.Fake<ISynchronizationManager>();
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_manager_is_null()
        {
            ThreadingExtensions.AcquireReadLock((ISynchronizationManager)null);
        }

        [Test]
        public void should_call_EnterReadLock_on_manager()
        {
            manager.AcquireReadLock();
            
            A.CallTo(() => manager.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_call_ExitReadLock_on_manager_when_disposed()
        {
            var @lock = manager.AcquireReadLock();
            @lock.Dispose();
            
            A.CallTo(() => manager.ExitReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_not_call_ExitReadLock_on_manager_when_not_disposed()
        {
            manager.AcquireReadLock();
            
            A.CallTo(() => manager.ExitReadLock()).MustNotHaveHappened();
        }

        [Test]
        public void should_call_ExitReadLock_only_once_even_if_Dispose_is_called_multiple_times()
        {
            var l = manager.AcquireReadLock();
            l.Dispose();
            l.Dispose();

            A.CallTo(() => manager.ExitReadLock()).MustHaveHappened(Repeated.Exactly.Once);
        }
    }

    [TestFixture]
    public class ISynchronizationManager_AcquireWriteLock
    {
        ISynchronizationManager manager;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            manager = A.Fake<ISynchronizationManager>();
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_manager_is_null()
        {
            ThreadingExtensions.AcquireWriteLock((ISynchronizationManager)null);
        }

        [Test]
        public void should_call_EnterWriteLock_on_manager()
        {
            manager.AcquireWriteLock();
            
            A.CallTo(() => manager.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_call_ExitWriteLock_on_manager_when_disposed()
        {
            var @lock = manager.AcquireWriteLock();
            @lock.Dispose();
            
            A.CallTo(() => manager.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_not_call_ExitWriteLock_on_manager_when_not_disposed()
        {
            manager.AcquireWriteLock();
            
            A.CallTo(() => manager.ExitWriteLock()).MustNotHaveHappened();
        }

        [Test]
        public void should_call_ExitWriteLock_only_once_even_if_Dispose_is_called_multiple_times()
        {
            var l = manager.AcquireWriteLock();
            l.Dispose();
            l.Dispose();
            
            A.CallTo(() => manager.ExitWriteLock()).MustHaveHappened(Repeated.Exactly.Once);
        }
    }

    [TestFixture]
    public class ISynchronizationManager_AcquireUpgradableReadLock
    {
        ISynchronizationManager manager;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            manager = A.Fake<ISynchronizationManager>();
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_manager_is_null()
        {
            ThreadingExtensions.AcquireUpgradableReadLock((ISynchronizationManager)null);
        }

        [Test]
        public void should_call_EnterReadLock_on_manager()
        {
            manager.AcquireUpgradableReadLock();
            
            A.CallTo(() => manager.EnterUpgradableReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_call_ExitUpgradableReadLock_on_manager_when_disposed()
        {
            var @lock = manager.AcquireUpgradableReadLock();
            @lock.Dispose();
           
            A.CallTo(() => manager.ExitUpgradableReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_not_call_ExitUpgradableReadLock_on_manager_when_not_disposed()
        {
            manager.AcquireUpgradableReadLock();
            
            A.CallTo(() => manager.ExitUpgradableReadLock()).MustNotHaveHappened();
        }

        [Test]
        public void should_call_ExitUpgradableReadLock_only_once_even_if_Dispose_is_called_multiple_times()
        {
            var l = manager.AcquireUpgradableReadLock();
            l.Dispose();
            l.Dispose();

            A.CallTo(() => manager.ExitUpgradableReadLock()).MustHaveHappened(Repeated.Exactly.Once);
        }

        [Test]
        public void should_call_EnterWriteLock_when_upgrade_is_called()
        {
            manager.AcquireUpgradableReadLock().Upgrade();
            
            A.CallTo(() => manager.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_call_ExitWriteLock_when_dispose_is_called_on_upgraded_lock()
        {
            var l = manager.AcquireUpgradableReadLock().Upgrade();
            l.Dispose();

            A.CallTo(() => manager.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_not_call_ExitWriteLock_when_dispose_is_called_on_upgraded_lock()
        {
            var l = manager.AcquireUpgradableReadLock().Upgrade();
            
            A.CallTo(() => manager.ExitWriteLock()).MustNotHaveHappened();
        }

        [Test]
        public void should_call_ExitWriteLock_only_once_even_if_Dispose_is_called_multiple_times_on_upgrade()
        {
            var l = manager.AcquireUpgradableReadLock().Upgrade();
            l.Dispose();
            l.Dispose();

            A.CallTo(() => manager.ExitWriteLock()).MustHaveHappened(Repeated.Exactly.Once);
        }
    }

    [TestFixture]
    public class ISynchronized_AcquireReadLock
    {
        ISynchronizationManager manager;
        ISynchronizable synchronized;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            manager = A.Fake<ISynchronizationManager>();
            synchronized = A.Fake<ISynchronizable>();
            
            A.CallTo(() => synchronized.SynchronizationManager).Returns(manager);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_manager_is_null()
        {
            ThreadingExtensions.AcquireReadLock((ISynchronizationManager)null);
        }

        [Test]
        public void should_call_EnterReadLock_on_manager()
        {
            synchronized.AcquireReadLock();
            A.CallTo(() => manager.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_call_ExitReadLock_on_manager_when_disposed()
        {
            var @lock = synchronized.AcquireReadLock();
            @lock.Dispose();

            A.CallTo(() => manager.ExitReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_not_call_ExitReadLock_on_manager_when_not_disposed()
        {
            synchronized.AcquireReadLock();
            A.CallTo(() => manager.ExitReadLock()).MustNotHaveHappened();
        }

        [Test]
        public void should_call_ExitReadLock_only_once_even_if_Dispose_is_called_multiple_times()
        {
            var l = synchronized.AcquireReadLock();
            l.Dispose();
            l.Dispose();

            A.CallTo(() => manager.ExitReadLock()).MustHaveHappened(Repeated.Exactly.Once);
        }
    }

    [TestFixture]
    public class ISynchronized_AcquireWriteLock
    {
        ISynchronizationManager manager;
        ISynchronizable synchronized;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            manager = A.Fake<ISynchronizationManager>();
            synchronized = A.Fake<ISynchronizable>();
            
            A.CallTo(() => synchronized.SynchronizationManager).Returns(manager);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_manager_is_null()
        {
            ThreadingExtensions.AcquireWriteLock((ISynchronizationManager)null);
        }

        [Test]
        public void should_call_EnterWriteLock_on_manager()
        {
            synchronized.AcquireWriteLock();
            A.CallTo(() => manager.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_call_ExitWriteLock_on_manager_when_disposed()
        {
            var @lock = synchronized.AcquireWriteLock();
            @lock.Dispose();
            A.CallTo(() => manager.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_not_call_ExitWriteLock_on_manager_when_not_disposed()
        {
            synchronized.AcquireWriteLock();
            A.CallTo(() => manager.ExitWriteLock()).MustNotHaveHappened();
        }

        [Test]
        public void should_call_ExitWriteLock_only_once_even_if_Dispose_is_called_multiple_times()
        {
            var l = synchronized.AcquireWriteLock();
            l.Dispose();
            l.Dispose();

            A.CallTo(() => manager.ExitWriteLock()).MustHaveHappened(Repeated.Exactly.Once);
        }
    }

    [TestFixture]
    public class ISynchronized_AcquireUpgradableReadLock
    {
        ISynchronizationManager manager;
        ISynchronizable synchronized;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            manager = A.Fake<ISynchronizationManager>();
            synchronized = A.Fake<ISynchronizable>();
            
            A.CallTo(() => synchronized.SynchronizationManager).Returns(manager);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_manager_is_null()
        {
            ThreadingExtensions.AcquireUpgradableReadLock((ISynchronizationManager)null);
        }

        [Test]
        public void should_call_EnterReadLock_on_manager()
        {
            synchronized.AcquireUpgradableReadLock();
            A.CallTo(() => manager.EnterUpgradableReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_call_ExitUpgradableReadLock_on_manager_when_disposed()
        {
            var @lock = synchronized.AcquireUpgradableReadLock();
            @lock.Dispose();
            A.CallTo(() => manager.ExitUpgradableReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_not_call_ExitUpgradableReadLock_on_manager_when_not_disposed()
        {
            synchronized.AcquireUpgradableReadLock();
            
            A.CallTo(() => manager.ExitUpgradableReadLock()).MustNotHaveHappened();
        }

        [Test]
        public void should_call_ExitUpgradableReadLock_only_once_even_if_Dispose_is_called_multiple_times()
        {
            var l = synchronized.AcquireUpgradableReadLock();
            l.Dispose();
            l.Dispose();

            A.CallTo(() => manager.ExitUpgradableReadLock()).MustHaveHappened(Repeated.Exactly.Once);
        }

        [Test]
        public void should_call_EnterWriteLock_when_upgrade_is_called()
        {
            synchronized.AcquireUpgradableReadLock().Upgrade();
            A.CallTo(() => manager.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_call_ExitWriteLock_when_dispose_is_called_on_upgraded_lock()
        {
            var l = synchronized.AcquireUpgradableReadLock().Upgrade();
            l.Dispose();

            A.CallTo(() => manager.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_not_call_ExitWriteLock_when_dispose_is_called_on_upgraded_lock()
        {
            var l = synchronized.AcquireUpgradableReadLock().Upgrade();
            A.CallTo(() => manager.ExitWriteLock()).MustNotHaveHappened();
        }

        [Test]
        public void should_call_ExitWriteLock_only_once_even_if_Dispose_is_called_multiple_times_on_upgrade()
        {
            var l = synchronized.AcquireUpgradableReadLock().Upgrade();
            l.Dispose();
            l.Dispose();

            A.CallTo(() => manager.ExitWriteLock()).MustHaveHappened(Repeated.Exactly.Once);
        }
    }

    public class lkdjfk
    {
        public void expectation_of_test()
        {
            MonitorSynchronizationManager manager = new MonitorSynchronizationManager(new object());
            var c = new SynchronizedCollection<string>(new List<string>(), manager);

            using (c.AcquireWriteLock())
            {
                c.Add("test");
                c.Remove("test");
            }

            using (var l = c.AcquireUpgradableReadLock())
            {
                if (!c.Contains("foo"))
                {
                    using (l.Upgrade())
                    {
                        c.Add("foo");
                    }
                }
            }
        }
    }

}